const axios = require('axios')
const rewire = require('rewire')
const R = require('ramda')
const {uuid} = rewire('../common/utils.cjs')
const proxy = rewire('../common/proxy.cjs')
const {errorRetry} = rewire('../common/combinator.cjs')
const {getDb} = rewire('../state.cjs')
const {getLogger} = rewire('../common/logger.cjs')
const fs = require('fs')


const logger = getLogger('fetch')
const wm = new WeakMap();

/**
 * 生成一个fetcher
 * @param method 
 * @returns async (url) => {axios...}
 */
function makeFetcher(method = 'get') {
    const config = {
        method,
        validateStatus: function (status) {
            return true
        },
    }
    
    try {
        return makeAxios(config)
    } catch(error) {
        throw new Error(error.message)
    }
}

function makeDownloadFn(fetcher) {
    const config = getConfig(fetcher)
    const cConfig = {...config, ...{responseType: 'stream'}}
    const fn = makeAxios(cConfig)
    return async function(url, path) {
        const response = await fn(url)
        response.data.pipe(fs.createWriteStream(path));
    }
}

function withConfig(fetcher, newConfig) {
    const config = getConfig(fetcher)
    if (config) {
        const cConfig = {...config, ...newConfig}
        return makeAxios(cConfig)
    } else {
        throw new Error("fetcher is invaild!");
    }
}

function header(fetcher, headersFn) {
    return withConfig(fetcher, {headers: headersFn()})
}

function baseURL(fetcher, baseUrl) {
    const config = getConfig(fetcher)
    if (config) {
        const newConfig = {baseURL: baseUrl, ...config}
        return makeAxios(newConfig)
    } else {
        throw new Error("fetcher is invaild!");
    }
}


// TODO 返回的页面数据有可能是错误，这时候不应该保存到数据库。
function wrapCache(fetcher, domain, onCache = null, onFetch = null) {
    const fn = async (url) => {
        // 如果数据库有数据，则直接返回，否则调用fetcher逻辑
        
        const dbdata = await getDb().select('html').from('spider_log').where('url', url).where('domain', domain).queryList()
        let html = null
        if (R.isEmpty(dbdata)) {
            const result = await fetcher(url)
            html = result['data']

            // 异步环境下有可能出现create失败问题，所以要try
            try {
                await getDb().insert("spider_log", {id: uuid(), url, html, domain}).execute()
            } catch(error) {
                // 如果遇到异常，打印并忽略
                logger.warn(error)
            }
            
            if (onFetch) {
                await onFetch(html)
            }
        }
        else {
            html = dbdata[0].html
            if (onCache) {
                await onCache(html)
            }
        }
        return {status: 200, data: html}
        
    }
    return fn
}

// 包裹一个代理
function wrapProxy(fetcher, num) {
    const config = getConfig(fetcher)
    const fn = async (url) => {
        const retry = errorRetry(async () => {
            const proxyConfig = await proxy.getDouyaProxyConfig(num)
            const ax = makeAxios({...config, ...proxyConfig})
            const rs = await ax(url)
            if (rs.data.includes("407 Proxy Authentication Required")) {
                // 重置代理上下文中的当前无效的IP
                await proxy.getDouyaProxyConfig(num, true)
                throw new Error('代理IP无效')
            }
            return rs
        }, 3, error => logger.error(error.message), 1000)

        return await retry()
    }

    setConfig(fn, config)
    return fn
}


function makeAxios(config) {
    const newConfig = {timeout: 1000, ...config}
    const fn = async (url) => {
        const resp = await axios({
            url,
            ...newConfig
        })
        return {status: resp.status, data: resp.data}
    }
    setConfig(fn, newConfig)
    return fn
}

function getConfig(obj) {
    return wm.get(obj)
}

function setConfig(obj, config) {
    wm.set(obj, config)
}


module.exports = {makeFetcher, header, baseURL, wrapCache, wrapProxy, withConfig, makeAxios, makeDownloadFn}